package top.cardone.security.spring.security.config;

import com.google.common.collect.Maps;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.PatternMatchUtils;
import top.cardone.cache.Cache;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.core.util.func.Func0;
import top.cardone.security.subject.Subject;
import top.cardone.security.utils.SecurityUtils;

import javax.annotation.PostConstruct;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
public class CardoneSpringSecurityConfig {
    public static String defaultRolePrefix = "ROLE_";

    @Bean
    @ConfigurationProperties(prefix = "cardone.security.spring-security.user-cache")
    public UserCache springSecurityUserCache() {
        return new UserCache() {
            private final String cacheName = "springSecurityUser";

            @Override
            public UserDetails getUserFromCache(String username) {
                return ApplicationContextHolder.getBean(Cache.class).get(UserDetails.class, cacheName, username);
            }

            @Override
            public void putUserInCache(UserDetails userDetails) {
                ApplicationContextHolder.getBean(Cache.class).put(cacheName, userDetails.getUsername(), userDetails);
            }

            @Override
            public void removeUserFromCache(String username) {
                ApplicationContextHolder.getBean(Cache.class).evict(cacheName, username);
            }
        };
    }

    @Bean
    public Func0<String> readPrincipalFunc() {
        return (Func0<String>) () -> top.cardone.context.util.StringUtils.defaultIfBlank(String.valueOf(SecurityUtils.getSubject().getPrincipal()), "admin-default");
    }

    @PostConstruct
    private void initSecurityUtils() {
        SecurityUtils.setSubject(new Subject() {
            @Override
            public Object getPrincipal() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

                if (authentication == null) {
                    return null;
                }

                return authentication.getName();
            }

            @Override
            public Object getDetails(String key) {
                return this.getDetails().get(key);
            }

            @Override
            public Map<String, Object> getDetails() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

                if (authentication == null ||
                        authentication.getDetails() == null) {
                    return Maps.newHashMap();
                }

                return (Map<String, Object>) authentication.getDetails();
            }

            @Override
            public <T> T getDetails(String key, Class<T> requiredType) {
                return requiredType.cast(this.getDetails().get(key));
            }

            @Override
            public boolean isPermitted(String permission) {
                return this.isPermittedAll(permission);
            }

            @Override
            public boolean isPermittedAll(String... permissions) {
                return this.getAuthorities()
                        .stream()
                        .filter(it -> !org.apache.commons.lang3.StringUtils.startsWith(it, defaultRolePrefix))
                        .anyMatch(it -> PatternMatchUtils.simpleMatch(permissions, it));
            }

            @Override
            public boolean hasRole(String roleIdentifier) {
                return this.hasAllRoles(roleIdentifier);
            }

            @Override
            public boolean hasAllRoles(String... roleIdentifiers) {
                return this.getAuthorities()
                        .stream()
                        .filter(it -> org.apache.commons.lang3.StringUtils.startsWith(it, defaultRolePrefix))
                        .anyMatch(it -> ArrayUtils.contains(roleIdentifiers, StringUtils.substring(it, defaultRolePrefix.length())));
            }

            @Override
            public boolean isAuthenticated() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

                if (authentication == null) {
                    return false;
                }

                return authentication.isAuthenticated();
            }

            @Override
            public void logout() {
                SecurityContextHolder.getContext().setAuthentication(null);
            }

            @Override
            public Collection<String> getAuthorities() {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

                if (authentication == null) {
                    return Collections.emptyList();
                }

                return authentication.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority)
                        .filter(it -> StringUtils.isNotBlank(it))
                        .collect(Collectors.toList());
            }
        });
    }
}


